package com.bluexmicro.module_componment.scan

import android.Manifest
import android.annotation.SuppressLint
import android.app.Application
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
import android.bluetooth.le.BluetoothLeScanner
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings
import android.content.Context
import android.content.pm.PackageManager
import android.location.LocationManager
import android.os.Build
import android.provider.Settings
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.*

@Suppress("UNCHECKED_CAST")
class ScanViewModelFactory(private val application: Application) : ViewModelProvider.Factory {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return ScanViewModel(application) as T
    }

}

class ScanViewModel(private val application: Application) : ViewModel() {

    private val bluetoothAdapter: BluetoothAdapter?

    private val _conditions = MutableStateFlow(ConditionModel())
    val conditions: StateFlow<ConditionModel> = _conditions

    private val devicesCache = DeviceModel()
    private val _displayDevices = MutableStateFlow(emptyList<BleDevice>())
    val displayDevices: StateFlow<List<BleDevice>> = _displayDevices

    private val _filter = MutableStateFlow(FilterModel())
    val filters: StateFlow<FilterModel> = _filter

    private val _selectedDevice = MutableSharedFlow<ScanResult>()
    val selectedDevice: SharedFlow<ScanResult> = _selectedDevice

    init {
        //每次开始前获取BluetoothAdapter原因是，当蓝牙没有打开时adapter不可用的
        val manager = application.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?
        bluetoothAdapter = manager?.adapter

        checkConditions()
    }

    fun pickSomeOne(device: BleDevice) {
        viewModelScope.launch {
            _selectedDevice.emit(device.scanResult)
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    // 权限
    ///////////////////////////////////////////////////////////////////////////


    fun checkConditions() {
        val bluetoothReady = bluetoothAdapter?.isEnabled ?: false
        val locationReady = application.checkLocation()
        val scanPermissionGranted = application.checkScanPermission()
        val connectPermissionGranted = application.checkConnectPermission()
        val locationPermissionGranted = application.checkLocationPermission()

        val conditions = ConditionModel().apply {
            bluetoothEnable = bluetoothReady
            locationEnable = locationReady
            locationPermission = locationPermissionGranted
            bluetoothScanPermission = scanPermissionGranted
            bluetoothConnectPermission = connectPermissionGranted
        }
        if (conditions.isScanReady()) {
            //当条件满足开始扫描
            setupScan()
        }
        _conditions.update {
            if (it.isSame(conditions)) it else conditions
        }
    }

    @SuppressLint("MissingPermission")
    fun enableBluetooth() = viewModelScope.launch(Dispatchers.Default) {
        bluetoothAdapter?.apply {
            if (!enable()) return@apply
            for (i in 0..5) {
                delay(1000)
                if (isEnabled) {
                    _conditions.update {
                        it.bluetoothEnable = true
                        it.clone()
                    }
                    break
                }
            }
            checkConditions()
        }
    }

    private fun Context.checkLocation(): Boolean {
        val locationManager =
            (getSystemService(Context.LOCATION_SERVICE) as LocationManager?) ?: return false
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            locationManager.isLocationEnabled
        } else {
            try {
                val state = Settings.Secure.getInt(contentResolver, Settings.Secure.LOCATION_MODE)
                state != Settings.Secure.LOCATION_MODE_OFF
            } catch (e: Settings.SettingNotFoundException) {
                e.printStackTrace()
                false
            }
        }
    }

    private fun Context.checkScanPermission(): Boolean {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            ActivityCompat.checkSelfPermission(
                this,
                Manifest.permission.BLUETOOTH_SCAN
            ) == PackageManager.PERMISSION_GRANTED
        } else {
            true
        }
    }

    private fun Context.checkConnectPermission(): Boolean {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            ActivityCompat.checkSelfPermission(
                this,
                Manifest.permission.BLUETOOTH_CONNECT
            ) == PackageManager.PERMISSION_GRANTED
        } else {
            true
        }
    }

    private fun Context.checkLocationPermission(): Boolean {
        return ActivityCompat.checkSelfPermission(
            this, Manifest.permission.ACCESS_FINE_LOCATION
        ) == PackageManager.PERMISSION_GRANTED
    }

    ///////////////////////////////////////////////////////////////////////////
    // 列表
    ///////////////////////////////////////////////////////////////////////////

    private var scanJob: Job? = null
    private fun setupScan() {
        if (scanJob?.isActive == true) return
        Log.e("TAG", "setupScan: ")
        scanJob = viewModelScope.launch {
            scanResultFlow.catch {
                //no
            }.collect {
                devicesCache.append(it, filters.value)
                _displayDevices.update {
                    devicesCache.getDisplayDevices()
                }
            }
        }
    }

    @SuppressLint("MissingPermission")
    private val scanResultFlow = callbackFlow {
        val scanCallback = object : ScanCallback() {
            override fun onScanResult(callbackType: Int, result: ScanResult) {
                Log.e("TAG", "onScanResult: " + result.toString())
                trySend(listOf(result))
            }

            override fun onBatchScanResults(results: MutableList<ScanResult>) {
                Log.e("TAG", "onBatchScanResults: " + results.size)
                trySend(results)
            }

            override fun onScanFailed(errorCode: Int) {
                val exception = IllegalStateException("Scan Failed (errorCode: $errorCode)")
                cancel(CancellationException(exception.message, exception))
            }
        }
        val scanner: BluetoothLeScanner? = if (bluetoothAdapter == null) {
            val exception = IllegalStateException("Bluetooth Exception(maybe Bluetooth disable)")
            cancel(CancellationException(exception.message, exception))
            null
        } else {

            val scanner = bluetoothAdapter.bluetoothLeScanner
           val delay = if(bluetoothAdapter.isOffloadedScanBatchingSupported) 1000L else 0L
            val scanSettings = ScanSettings.Builder()
                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
                .setReportDelay(delay)
                .build()
            scanner?.startScan(null, scanSettings, scanCallback)
            scanner
        }
        awaitClose {
            scanner?.stopScan(scanCallback)
        }
    }


    ///////////////////////////////////////////////////////////////////////////
    // filter
    ///////////////////////////////////////////////////////////////////////////


    fun saveFilters(rssi: Int, connectable: Boolean, flag: String?) {
        _filter.update {
            FilterModel(rssi, connectable, flag, false)
        }
    }

    fun filterAndSortList() = viewModelScope.launch {
        devicesCache.filterAndSort(_filter.value)
        _displayDevices.update {
            devicesCache.getDisplayDevices()
        }
    }

    fun setConfiguring(configuring: Boolean) {
        _filter.update {
            FilterModel(it.rssiThreshold, it.connectable, it.flag, configuring)
        }
    }


}

